package com.cysyz.mylib.dao.support;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class NativeQueryResultsMapper {

    private static Map<Class, List<Field>> cachedFieldInfo = new HashMap<>();

    private static Logger log = LoggerFactory.getLogger(NativeQueryResultsMapper.class);

    public static <T> List<T> map(List<Object[]> objectArrayList, Class<T> genericType) {
        List<T> ret = new ArrayList<T>();
        List<Field> mappingFields = getNativeQueryResultColumnAnnotatedFields(genericType);
        try {
            for (Object[] objectArr : objectArrayList) {
                T t = genericType.newInstance();
                for (int i = 0; i < objectArr.length; i++) {
                    try {
                        PropertyDescriptor propertyDescriptor = new PropertyDescriptor(mappingFields.get(i).getName(), t.getClass());
                        Method writeMethod = propertyDescriptor.getWriteMethod();

                        Object sourceValue = convertSourceValueIfNeed(propertyDescriptor, objectArr[i]);

                        writeMethod.invoke(t, sourceValue);

                    } catch (IntrospectionException e) {
                        e.printStackTrace();
                    }
//                    BeanUtils.setProperty(t, mappingFields.get(i).getName(), objectArr[i]);
                }
                ret.add(t);
            }
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        return ret;
    }

    private static Object convertSourceValueIfNeed(PropertyDescriptor propertyDescriptor, Object sourceValue) {
        Class<?> sourceValueType = sourceValue !=null ? sourceValue.getClass(): null;
        Class<?> targetPropertyType = propertyDescriptor.getPropertyType();


        if (targetPropertyType == String.class && sourceValueType != null & sourceValueType != String.class) {
            sourceValue = sourceValue.toString();
        }
        return sourceValue;
    }

    // Get ordered list of fields
    private static <T> List<Field> getNativeQueryResultColumnAnnotatedFields(Class<T> genericType) {

        List<Field> orderedFields = null;
        synchronized (cachedFieldInfo) {
            orderedFields = cachedFieldInfo.get(genericType);
        }

        if (orderedFields == null) {
            Field[] fields = genericType.getDeclaredFields();
            orderedFields = Arrays.asList(new Field[fields.length]);
            for (int i = 0; i < fields.length; i++) {
                if (fields[i].isAnnotationPresent(NativeQueryResultColumn.class)) {
                    NativeQueryResultColumn nqrc = fields[i].getAnnotation(NativeQueryResultColumn.class);
                    orderedFields.set(nqrc.index(), fields[i]);
                }
            }

            synchronized (cachedFieldInfo) {
                cachedFieldInfo.put(genericType, orderedFields);
            }
        }

        return orderedFields;
    }
}